﻿using Bouyei.Geo.Geometries;
using System;
using System.Collections.Generic;

namespace Bouyei.Geo.GeoParsers.EsirMdb
{
    /// <summary>
    /// mdb,gdb字节解析
    /// </summary>
    public class GeometryReader:BaseBytes
    {
        public GeoType GeometryType { get; set; }

        public Coordinate Min { get; set; }

        public Coordinate Max { get; set; }

        public GeometryReader(byte[] buffer,int offset=0)
            : base(buffer,ByteOrder.LittleEndian,offset)
        {
           
        }

        public unsafe Geometry Reader()
        {
            fixed (byte* ptr = &Buffer[0])
            {
                //open api和arcgis 有区别
                int type = bitExt.ToInt32(ptr) & 0xFFF;
                switch (type)
                {
                    case 1:
                    case 9:
                    case 11:
                    case 21:
                    case 52://open api
                        GeometryType = GeoType.POINT;
                        break;
                    case 8:
                    case 18:
                    case 20:
                    case 28:
                    case 53://open api
                        GeometryType = GeoType.MULTIPOINT;
                        break;
                    case 3:
                    case 10:
                    case 13:
                    case 23:
                    case 50://open api 536870962
                        GeometryType = GeoType.LINESTRING;
                        break;
                    case 5:
                    case 15:
                    case 19:
                    case 25:
                    case 51://open api 536870963
                        GeometryType = GeoType.POLYGON;
                        break;
                    default:
                        throw new Exception("GeometryType not supported:" + type);
                }

                if (GeometryType == GeoType.POINT)
                {
                    var coord = new Coordinate()
                    {
                        X = bitExt.ToDouble(ptr + 4),
                        Y = bitExt.ToDouble(ptr + 12)
                    };
                    return new Geometry(coord);
                }

                Min = new Coordinate()
                {
                    X = bitExt.ToDouble(ptr + 4),
                    Y = bitExt.ToDouble(ptr + 12)
                };
                Max = new Coordinate()
                {
                    X = bitExt.ToDouble(ptr + 20),
                    Y = bitExt.ToDouble(ptr + 28)
                };
                if (GeometryType == GeoType.MULTIPOINT)
                {
                    var coordinates = ParserToMultiPoint(ptr + 36);
                    return new Geometry(GeoType.MULTIPOINT, coordinates);
                }
                else
                {
                    var coordinates = ParserToArray(ptr + 36);
                    if ((GeometryType == GeoType.POLYGON || GeometryType == GeoType.POLYGONZ)
                        && coordinates.Count > 1)
                    {
                        GeometryType = GeoType.MULTIPOLYGON;
                    }

                    if ((GeometryType == GeoType.LINESTRING || GeometryType == GeoType.LINESTRINGZ)
                        && coordinates[0].Count > 1)
                    {
                        GeometryType = GeoType.MULTILINESTRING;
                    }
                    return new Geometry(GeometryType, coordinates);
                }
            }
        }

        private unsafe List<List<GeoSequence>> ParserToArray(byte* ptr)
        {
            int count = bitExt.ToInt32(ptr);
            long numvertex = bitExt.ToInt64(ptr + 4);//total vertex number

            List<List<GeoSequence>> geoBuffer = new List<List<GeoSequence>>(2);

            List<int> rings = new List<int>(count);
            List<GeoSequence> holes = new List<GeoSequence>(1);
            List<GeoSequence> shellRings = new List<GeoSequence>(1);

            int start = 12;

            //first ring
            int outerNumber = (int)numvertex;
            if (count <= 1)
            {
                rings.Add(outerNumber);
            }
            else
            {
                outerNumber = bitExt.ToInt32(ptr + start);
                start += 4;
                rings.Add(outerNumber);

                //ring
                int lastRingNum = outerNumber;
                for (int i = 0; i < count - 2; ++i)
                {
                    int ringNum = bitExt.ToInt32(ptr + start);
                    start += 4;

                    rings.Add(ringNum - lastRingNum);
                    lastRingNum = ringNum;
                }

                //last ring
                int lastNum = (int)(numvertex - lastRingNum);
                rings.Add(lastNum);
            }

            //coordinates 
            for (int j = 0; j < count; ++j)
            {
                var coords = new Coordinate[rings[j]];

                for (int i = 0; i < coords.Length; ++i)
                {
                    var lon = bitExt.ToDouble(ptr + start);
                    var lat = bitExt.ToDouble(ptr + start + 8);
                    coords[i] = new Coordinate() { X = lon, Y = lat };

                    start += 16;
                }

                var seq = new GeoSequence(coords);
                var orientation = seq.GetOrientation();

                if (orientation == GeoOrientation.Clockwise
                    || shellRings.Count == 0)
                {
                    if (shellRings.Count > 0)
                    {
                        shellRings.AddRange(holes);

                        geoBuffer.Add(new List<GeoSequence>(shellRings));
                        holes.Clear();
                        shellRings.Clear();
                    }
                    shellRings.Add(seq);
                }
                else
                {
                    holes.Add(seq);
                }
            }

            if (shellRings.Count > 0)
                geoBuffer.Add(new List<GeoSequence>(shellRings));

            if (holes.Count > 0)
                geoBuffer.Add(new List<GeoSequence>(holes));

            return geoBuffer;
        }

        private unsafe Coordinate[] ParserToMultiPoint(byte* ptr)
        {
            int count = bitExt.ToInt32(ptr);
            Coordinate[] coords = new Coordinate[count];
            int start = 4;

            for (int i = 0; i < count; ++i)
            {
                var lon = bitExt.ToDouble(ptr + start);
                var lat = bitExt.ToDouble(ptr + start + 8);
                coords[i] = new Coordinate() { X = lon, Y = lat };

                start += 16;
            }
            return coords;
        }
    }
}
